# -*- Ruby -*-

%% {
##################################################### 
  # Structure to hold composite method names
  SymbolEntry = Struct.new(:type, :name, :chain)


  # Structure to hold position information
  Position = Struct.new(:container_type, :container,
                       :position_type,  :position)

  # Structure to hold breakpoint information
  Breakpoint = Struct.new(:position, :negate, :condition)

  # Structure to hold list information
  List = Struct.new(:position, :num)

  DEFAULT_OPTS = {
    :debug=>false, 
    :file_exists_proc => Proc.new{|filename|
        File.readable?(filename) && !File.directory?(filename)
    }
  }
  def initialize(str, opts={})
    @opts = DEFAULT_OPTS.merge(opts)
    setup_parser(str, opts[:debug])
    @file_exists_proc = @opts[:file_exists_proc]
  end


   }
#####################################################

      upcase_letter = /[A-Z]/
    downcase_letter = /[a-z]/
      suffix_letter = /[=!?]/ 
             letter = upcase_letter
                    | downcase_letter
          id_symbol = letter | "_" | [0-9]


# An variable or a method identifier
# Examples: 
#   var1
#   my_var? 
# But not: Variable or @var
vm_identifier = < (downcase_letter | "_") id_symbol* suffix_letter? >
    { 
      SymbolEntry.new(:variable, text)
    }

# Examples: 
#   var1
# But not: my_var?, my_var! 
variable_identifier = < (downcase_letter | "_") id_symbol* >
    { 
      SymbolEntry.new(:variable, text)
    }

# Examples: 
#   MY_CONSTANT
#   MyConstant_01
# But not:
#   MyConstant_01?
constant_identifier = < upcase_letter id_symbol* >
    { 
      SymbolEntry.new(:constant, text)
    }

# Examples:
#   $global_variable
# We won't try for funny global names like $$, $? $:, $', etc
global_identifier = < "$" (constant_identifier | variable_identifier) >
    {
      SymbolEntry.new(:global, text)
    }

# Examples: 
#   Foo
#   foo
# But not:
#   foo!, @foo, Class.foo
local_internal_identifier = constant_identifier | variable_identifier

# Examples: 
#   Foo, foo, foo!
#   foo
# But not:
#   @foo, Class.foo
local_identifier = constant_identifier | vm_identifier

# Example: @foo
instance_identifier = < '@' local_identifier >
    {
      SymbolEntry.new(:instance, text)
    }     


# Example: @@foo
classvar_identifier = ('@@' local_identifier:id )
    {
     SymbolEntry.new(:classvar, id)
    }     

identifier = global_identifier 
       | instance_identifier 
       | classvar_identifier 
       | local_identifier 

id_separator = < '::'|'.' > { text }

# Like of class_module_chain *after* the first name. So we don't
# allow sigils in the initial id. That is we don't allow:
#   Class.@name1.@@name2.$name3  
# But we do allow final sigils:
#   class.name!, class.name=
internal_class_module_chain = 
    < local_internal_identifier:parent id_separator:sep  
      internal_class_module_chain:child >
      {
         SymbolEntry.new(parent.type, text, [parent, child, sep])
      }
    | local_identifier


# I think strict Ruby rules are that once one goes from :: to .
# There is no going back. That is, A.B::C is invalid. 
# 
# Also I think method names can't be constants. But such 
# subtleties we'll handle when we process the final structure.
# Examples:
#  Object, A::B, A.b @@foo.bar, $foo.bar.baz? 

class_module_chain = 
    < identifier:parent id_separator:sep  internal_class_module_chain:child >
       {
         SymbolEntry.new(parent.type, text, [parent, child, sep])
      }
    | identifier

##############################################################
# Location-specific things. This is used in conjunction with
# method-like things above.
          sp     = /[ \t]/
           -     = sp+
     dbl_escapes = "\\\"" { '"' }
                 | "\\n"  { "\n" }
                 | "\\t"  { "\t" }
                 | "\\\\" { "\\" }
     escapes    = "\\\"" { '"' }
                 | "\\n"  { "\n" }
                 | "\\t"  { "\t" }
                 | "\\ "  { " " }
                 | "\\:"  { ":" }
                 | "\\\\" { "\\" }
   dbl_seq       = < /[^\\"]+/ > { text }
   dbl_not_quote = (dbl_escapes | dbl_seq)+:ary { ary }
      dbl_string = "\"" dbl_not_quote:ary "\"" { ary.join }
not_space_colon  = escapes 
                 | < /[^ \t\n:]/ > { text }
not_space_colons = ( not_space_colon )+:ary { ary.join }
    filename    = dbl_string | not_space_colons
file_pos_sep    = sp+ | ':'
     integer    = </[0-9]+/> { text.to_i }
 line_number    = integer

vm_offset = '@' integer:int
  {
    Position.new(nil, nil, :offset, int)
  }

# Examples:
#  @43
#  5
position = 
  vm_offset 
  | line_number:l { 
  Position.new(nil, nil, :line, l) 
}

file_colon_line = file_no_colon:file &{ File.exist?(file) } ':' position:pos { 
  Position.new(:file, file, pos.position_type, pos.position) 
}

# Examples:
#   Myclass.fn @5  # bytecode offset 5 of fn
#   Myclass.fn:@5  # same as above
#   Myclass.fn 5   # line number 5 of fn
# Note: Myclass.fn could be either a filename or a method name

# The below ordering is important. 
# 1. Numbers can't be method names they are first. If there's a 
#    file with that name, later we'll allow quoting to indicate filename.
# 2. filename:position can't also be a method so that's next
# 3. It is possible a filename can be a method name, but we
#    test using File.exist? so we want to put this first.
#    Later "quoting" will skip the File.exist?
# 4. Class module *with* a position is next and has to be before
#    without a position, else we would stop early before handling
#    the position.

location = 
   position 
 | <filename>:file &{ @file_exists_proc.call(file) } file_pos_sep position:pos {
      Position.new(:file, file, pos.position_type, pos.position)
    }
 | <filename>:file &{ @file_exists_proc.call(file) } {
      Position.new(:file, file, nil, nil)
    }
 | class_module_chain?:fn file_pos_sep position:pos {
      Position.new(:fn, fn, pos.position_type, pos.position)
    }
 | class_module_chain?:fn  {
      Position.new(:fn, fn, nil, nil)
    }

if_unless = <"if" | "unless"> { text }
condition = </.+/> { text}

breakpoint_stmt_no_condition = location:loc  {
  Breakpoint.new(loc, false, 'true')
}

# Note that the first word "break" is handled in the command.
# Also, "break" with nothing else is handled there as well
breakpoint_stmt = location:loc - if_unless:iu - condition:cond {
     Breakpoint.new(loc, iu == 'unless', cond)
}
 | breakpoint_stmt_no_condition

# Note that the first word "list", "list>" or handled in
# the command.  Also, "list" with nothing else is
# handled there as well
list_special_targets = <'.' | '-'> { text }
list_stmt = (list_special_targets | location):loc - (integer:int)? {
  List.new(loc, int)
}
  | (list_special_targets | location):loc {
  List.new(loc, nil)
}
